<!DOCTYPE html>
<html lang="en">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/favicon.jpg">
  <link rel="icon" type="image/png" href="/img/favicon.jpg">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="">
  <meta name="author" content="John Doe">
  <meta name="keywords" content="">
  <title>从输入URL到浏览器显示页面的流程 - Nyima&#39;s Blog</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/lib/hint/hint.min.css" />

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.0.0/styles/github-gist.min.css" />
  

  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_yg9cfy8wd6.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_pjno9b9zyxs.css">


<link  rel="stylesheet" href="/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script  src="/js/utils.js" ></script>
<meta name="generator" content="Hexo 4.2.1"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/">&nbsp;<strong>Nyima</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                Home
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                Archives
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                Categories
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                Tags
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/links/">
                <i class="iconfont icon-link-fill"></i>
                Links
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                About
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="view intro-2" id="background" parallax=true
         style="background: url('/img/3.jpg') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="container text-center white-text fadeInUp">
            <span class="h2" id="subtitle">
              
            </span>

            
              
  <div class="mt-3 post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2020-12-10 10:00">
      December 10, 2020 am
    </time>
  </div>


<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      6.8k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      73
       分钟
    </span>
  

  
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <div class="post-content mx-auto" id="post">
            
            <article class="markdown-body">
              <h1 id="从输入URL到浏览器显示页面的流程"><a href="#从输入URL到浏览器显示页面的流程" class="headerlink" title="从输入URL到浏览器显示页面的流程"></a>从输入URL到浏览器显示页面的流程</h1><p>当在浏览器中输入域名，敲下回车后，不一会儿浏览器就会显示我们想要的界面。本文将简单介绍这其中经历了什么过程。</p>
<p>注意：以下分析基于<strong>HTTP</strong>请求，并且Web容器使用<strong>Tomcat</strong>，后端框架使用<strong>SSM</strong></p>
<h2 id="一、URL解析"><a href="#一、URL解析" class="headerlink" title="一、URL解析"></a>一、URL解析</h2><h3 id="1、地址解析"><a href="#1、地址解析" class="headerlink" title="1、地址解析"></a><strong>1、地址解析</strong></h3><p>浏览器会根据你的输入来判断该输入是一条合法的URL，还是需要被搜索的关键词。并且根据你输入的内容进行自动完成、字符编码等操作。</p>
<h3 id="2、其他操作"><a href="#2、其他操作" class="headerlink" title="2、其他操作"></a>2、其他操作</h3><p>目前大部分浏览器都会<strong>强制客户端使用HTTPS协议</strong>以保证信息传输的安全性。同时还会进行一些额外的操作，比如安全检查、访问限制等。</p>
<h3 id="3、缓存检查"><a href="#3、缓存检查" class="headerlink" title="3、缓存检查"></a>3、缓存检查</h3><p>有时候博客在gitee上进行了更新，但是通过谷歌浏览器查看博客时，仍是更新前的博客，这是因为浏览器中缓存了之前的博客界面。</p>
<p>浏览器会先检测是否缓存了目标URL的页面，如果有且缓存未过期，则直接展示缓存页面，无需再向服务器进行请求。</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201208150315.png" srcset="/img/loading.gif" alt=""></p>
<h2 id="二、DNS解析"><a href="#二、DNS解析" class="headerlink" title="二、DNS解析"></a>二、DNS解析</h2><p><strong>DNS解析是寻找所需要的资源的IP地址的过程</strong>。因为互联网中每一台连网的机器都有<strong>唯一IP作为标识</strong>，但是它是一串数字，记忆太过困难。所以就需要将网址和IP地址进行转换，也就是DNS解析。其具体步骤如下。</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209141644.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="1、查询缓存"><a href="#1、查询缓存" class="headerlink" title="1、查询缓存"></a>1、查询缓存</h3><p>我们的浏览器、操作系统、路由器都会缓存一些URL对应的IP地址，统称为<strong>DNS高速缓存</strong>。这是为了加快DNS解析速度，使得不必每次都到根域名服务器中去查询。</p>
<h3 id="2、递归解析"><a href="#2、递归解析" class="headerlink" title="2、递归解析"></a>2、递归解析</h3><p>输入<code>www.baidu.com</code>网址后，首先在高速缓存中查找，没找到去根域名服务器查找，没有再去<code>com</code>顶级域名服务器查找，依次类推，直到找到IP地址，然后把它记录在本地告诉缓存中，供下次使用。</p>
<p>大致过程就是<code>.</code>-&gt; <code>.com</code> -&gt;<code>baidu.com.</code> -&gt; <code>www.baidu.com.</code></p>
<p>其中<code>.</code>代表根域名服务器。</p>
<h3 id="3、DNS负载均衡"><a href="#3、DNS负载均衡" class="headerlink" title="3、DNS负载均衡"></a>3、DNS负载均衡</h3><p>访问<code>baidu.com</code>的时候，每次响应的可能并非是同一个服务器（IP地址不同），一般大公司都有成百上千台服务器来支撑访问，DNS可以返回一个<strong>合适的机器的IP</strong>给用户，例如可以<strong>根据每台机器的负载量，该机器离用户地理位置的距离</strong>等等，这种过程就是DNS负载均衡。</p>
<h2 id="三、建立TCP连接"><a href="#三、建立TCP连接" class="headerlink" title="三、建立TCP连接"></a>三、建立TCP连接</h2><p>TCP/IP 分为四层，在发送数据时，<strong>每层都要对数据进行封装</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209141851.png" srcset="/img/loading.gif" alt=""></p>
<p>TCP提供一种<strong>面向连接的，可靠的字节流</strong>服务，是一种可靠传输。接下来将会讲解TCP的<strong>首部、三次握手与四次挥手</strong>。</p>
<h3 id="1、TCP的首部"><a href="#1、TCP的首部" class="headerlink" title="1、TCP的首部"></a>1、TCP的首部</h3><p>TCP首部的格式如下</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201208164319.png" srcset="/img/loading.gif" alt=""></p>
<ul>
<li><p><strong>源端口</strong>：源端口和IP地址的作用是标识<strong>报文的发送地址和返回地址</strong></p>
</li>
<li><p><strong>目的端口</strong>：端口指明<strong>接收方</strong>计算机上的应用程序接口</p>
<ul>
<li>TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP<strong>唯一确定</strong>一条TCP连接</li>
</ul>
</li>
<li><p><strong>序号</strong>：是TCP可靠传输的<strong>关键部分</strong></p>
<ul>
<li>序号是该报文段发送的数据组的<strong>第一个字节的序号</strong>。在TCP传送的流中，每<strong>一个字节都有一个序号</strong><ul>
<li>比如一个报文段的序号为300，报文段数据部分共有100字节，则<strong>下一个报文段</strong>的序号为400。所以序号确保了TCP传输的有序性</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>确认号：ack，用于指明下一个期待收到的字节序号</strong>，表明该序号之前的所有数据已经正确无误的收到</p>
<ul>
<li><strong>确认号只有当ACK标志为1时才有效</strong>。比如建立连接时，SYN报文的ACK标志位为0</li>
</ul>
</li>
<li><p><strong>首部长度/数据偏移</strong>：占4位，它指出<strong>TCP报文的数据距离TCP报文段的起始处有多远</strong></p>
</li>
<li><p><strong>保留</strong>：占6位，保留今后使用，但<strong>目前应都位0</strong></p>
</li>
<li><p><strong>控制位：URG ACK PSH RST SYN FIN</strong>，共6个，每一个标志位表示一个控制功能</p>
<ul>
<li>URG：紧急。当URG=1时，表明紧急指针字段有效。告诉系统<strong>此报文段中有紧急数据</strong></li>
<li>ACK：确认。<strong>当ACK=1时，确认号字段才有效</strong>。TCP规定，在连接建立后所有报文的传输都必须把ACK置1</li>
<li>PSH：推送。当两个应用进程进行交互式通信时，有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应，这时候就将PSH=1</li>
<li>RST：复位。当RST=1，表明TCP连接中出现严重差错，<strong>必须释放连接，然后再重新建立连接</strong></li>
<li>SYN：同步，在连接建立时用来同步序号。当SYN=1，ACK=0，表明是连接请求报文，若同意连接，则响应报文中应该使SYN=1，ACK=1</li>
<li>FIN：终止，用来释放连接。当FIN=1，表明此报文的发送方的数据已经发送完毕，并且要求释放</li>
</ul>
</li>
<li><p><strong>窗口</strong>：<strong>滑动窗口</strong>大小，用来告知发送端<strong>接受端的缓存大小</strong>，以此控制发送端发送数据的速率，从而达到流量控制。窗口大小时一个16bits字段，因而窗口大小最大为65535</p>
</li>
<li><p><strong>校验和</strong>：奇偶校验，此校验和是对整个的 TCP 报文段，包括 TCP 头部和 TCP 数据，以 16 位字进行计算所得。由发送端计算和存储，并由接收端进行验证</p>
</li>
<li><p><strong>紧急指针</strong>：只有当 <strong>URG 标志置 1 时紧急指针才有效</strong>。紧急指针是一个正的偏移量，和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式</p>
</li>
<li><p><strong>选项和填充</strong>：最常见的可选字段是最长报文大小，又称为MSS（Maximum Segment Size），每个连接方通常都在通信的第一个报文段（为建立连接而设置SYN标志为1的那个段）中指明这个选项，它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍，所以要加填充位，即在这个字段中加入额外的零，以保证TCP头是32的整数倍</p>
</li>
<li><p><strong>数据部分</strong>： TCP 报文段中的数据部分是可选的。<strong>在一个连接建立和一个连接终止时，双方交换的报文段仅有 TCP 首部</strong>。如果一方没有数据要发送，也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中，也会发送不带任何数据的报文段。</p>
</li>
</ul>
<h3 id="2、三次握手"><a href="#2、三次握手" class="headerlink" title="2、三次握手"></a>2、三次握手</h3><p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210127161838.png" srcset="/img/loading.gif" alt=""></p>
<p><strong>握手过程中传送的包里不包含数据</strong>，三次握手完毕后，客户端与服务器才正式开始传送数据。理想状态下，TCP连接一旦建立，在通信双方中的任何一方主动关闭连接之前，TCP 连接都将被一直保持下去。</p>
<p><strong>为什么是三次握手？两次不行吗？四次不行吗？</strong></p>
<h4 id="为什么不是两次握手"><a href="#为什么不是两次握手" class="headerlink" title="为什么不是两次握手"></a>为什么不是两次握手</h4><p>这是为了<strong>避免服务器建立无用连接</strong>（客户端服务器建立连接后，却不传输数据）</p>
<p>如果只进行两次握手，如果客户端向服务器第一次发送的建立连接的请求因为某原因，<strong>兜兜转转绕了一大圈才到达服务器</strong>。这期间客户端因为未收到服务器的响应，就会再次发送连接请求，这时服务器收到了，向客户端发送连接请求后，连接便建立了。然后数据传输完毕后，释放连接。<strong>这时刚刚兜兜转转一大圈的建立连接的请求到了服务器</strong>，服务器收到后再次向客户端发送请求，发送后又建立了连接，但是建立连接后客户端没有再理会服务器，客户端与服务器之间没有传输数据，此时服务器的资源就会被浪费</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201208172930.png" srcset="/img/loading.gif" alt=""></p>
<h4 id="为什么不是四次握手"><a href="#为什么不是四次握手" class="headerlink" title="为什么不是四次握手"></a>为什么不是四次握手</h4><p><strong>因为通信不可能100%可靠</strong>（红军蓝军约定）， 而上面的三次握手已经做好了通信的准备工作， <strong>再增加握手， 并不能显著提高可靠性</strong>，所以只需要三次握手就足够了</p>
<p>这里简单介绍一下<strong>红军蓝军约定</strong></p>
<blockquote>
<p>红军和蓝军都想消灭一波敌人，但是单凭他们一个军队的力量都不足以消灭这波敌人，因此他们想到了一起合作，于是红军向蓝军发了一封电报，内容是约定好早上8点一起向敌军进攻，由于他们不确定蓝军是否一定能收到电报, 所以只有收到蓝军的回复之后才会进行进攻，而蓝军也是同样的想法，因为他们不确定红军一定能收到自己的回复而在约定好的时间发动进攻，所以他们只有收到红军的回复后才发动进攻….</p>
<p>问怎样才能保证这次战役一定胜利呢？答案是不可能的，因为<strong>双方都对于自己发出的消息对方是否一定接收得到存在质疑</strong>，所以，这样的通信将一直进行下去，结果将是使胜利的几率一直接近100%，但是却永远达不到100%。</p>
</blockquote>
<h3 id="3、四次挥手"><a href="#3、四次挥手" class="headerlink" title="3、四次挥手"></a>3、四次挥手</h3><p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209144349.png" srcset="/img/loading.gif" alt=""></p>
<ul>
<li><strong>第一次挥手</strong><ul>
<li>客户端发送一个<strong>FIN=1</strong>，用来关闭客户端到服务器的数据传送，此后客户端不会再向服务器发送数据(当然，在fin包之前发送出去的数据，如果没有收到对应的ack确认报文，客户端依然会重发这些数据)，但是，此时客户端还可以接受数据。 FIN=1，其序列号为seq=u（等于前面已经传送过来的数据的最后一个字节的序号加1），此时，<strong>客户端进入FIN-WAIT-1</strong>（终止等待1）状态。 TCP规定，FIN报文段即使不携带数据，也要消耗一个序号</li>
</ul>
</li>
<li><strong>第二次挥手</strong><ul>
<li>服务器收到FIN包后，发送一个ACK给对方并且带上自己的序列号seq，确认序号为收到序号+1（与SYN相同，一个FIN占用一个序号）。此时，<strong>服务端就进入了CLOSE-WAIT</strong>（关闭等待）状态。TCP服务器通知高层的应用进程，客户端向服务器的方向就释放了，这时候处于半关闭状态，即客户端已经没有数据要发送了，<strong>但是服务器若发送数据，客户端依然要接受</strong>。这个状态还要持续一段时间，也就是整个CLOSE-WAIT状态持续的时间</li>
<li>此时，<strong>客户端就进入FIN-WAIT-2</strong>（终止等待2）状态，等待服务器发送连接释放报文（在这之前还需要接受服务器发送的最后的数据）</li>
</ul>
</li>
<li><strong>第三次挥手</strong><ul>
<li>服务器发送一个FIN=1，用来关闭服务器到客户端的数据传送，也就是通知客户端，可以真正地释放连接了。由于在半关闭状态，服务器很可能又发送了一些数据，假定此时的序列号为seq=w，此时，<strong>服务器就进入了LAST-ACK</strong>（最后确认）状态，等待客户端的确认</li>
</ul>
</li>
<li><strong>第四次挥手</strong><ul>
<li>客户端收到FIN后，发送一个ACK=1给服务器，确认序号为收到序号+1，此时，<strong>客户端就进入了TIME-WAIT</strong>（时间等待）状态。注意此时TCP连接还没有释放，<strong>必须经过2MSL（最长报文段寿命）的时间后</strong>，当客户端撤销相应的TCB后，才进入CLOSED状态</li>
<li><strong>服务器只要收到了客户端发出的确认，立即进入CLOSED状态</strong>。同样，撤销TCB后，就结束了这次的TCP连接。可以看到，服务器结束TCP连接的时间要比客户端早一些</li>
</ul>
</li>
</ul>
<h4 id="为什么客户端最后还要等待2MSL"><a href="#为什么客户端最后还要等待2MSL" class="headerlink" title="为什么客户端最后还要等待2MSL"></a>为什么客户端最后还要等待2MSL</h4><p><strong>确保第四次挥手服务器能够收到，同时使失效的连接请求从网络中消失</strong></p>
<p><strong>MSL</strong>是Maximum Segment Lifetime英文的缩写，中文可以译为<strong>报文最大生存时间</strong>，他是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。</p>
<ul>
<li><p><strong>保证客户端发送的最后一个ACK报文能够到达服务器</strong>，因为这个ACK报文可能丢失。站在服务器的角度看来，我已经发送了FIN+ACK报文请求断开了，客户端还没有给我回应，应该是我发送的请求断开报文它没有收到，<strong>于是服务器又会重新发送一次</strong>，而客户端就能在这个2MSL时间段内收到这个重传的报文，接着给出回应报文，并且会<strong>重启2MSL计时器</strong></p>
</li>
<li><p>防止类似与三次握手中提到了的<strong>已经失效的连接请求报文段</strong>出现在本连接中。客户端发送完最后一个确认报文后，在这个2MSL时间中，就可以<strong>使本连接持续的时间内所产生的所有报文段都从网络中消失</strong>（最长生存MSL）。<strong>这样新的连接中不会出现旧连接的请求报文</strong></p>
</li>
</ul>
<h4 id="为什么建立连接是三次握手，关闭连接确是四次挥手"><a href="#为什么建立连接是三次握手，关闭连接确是四次挥手" class="headerlink" title="为什么建立连接是三次握手，关闭连接确是四次挥手"></a>为什么建立连接是三次握手，关闭连接确是四次挥手</h4><ul>
<li><p>建立连接的时候， 服务器在LISTEN状态下，收到建立连接请求的SYN报文后，<strong>把ACK和SYN放在一个报文里发送给客户端</strong></p>
</li>
<li><p>关闭连接时，服务器收到对方的FIN报文时，仅仅表示对方不再发送数据了但是还能接收数据，而自己也未必全部数据都发送给对方了，所以己方可以立即关闭，也可以发送一些数据给对方后，再发送FIN报文给对方来表示同意现在关闭连接，因此，<strong>己方ACK和FIN一般都会分开发送，从而导致多了一次</strong></p>
</li>
</ul>
<h2 id="四、发送HTTPS请求"><a href="#四、发送HTTPS请求" class="headerlink" title="四、发送HTTPS请求"></a>四、发送HTTPS请求</h2><h3 id="1、HTTP简介"><a href="#1、HTTP简介" class="headerlink" title="1、HTTP简介"></a>1、HTTP简介</h3><p>参考<a href="https://mp.weixin.qq.com/s/AK1Pb9rx0q5Hf8dq6HNOhw" target="_blank" rel="noopener"><strong>你每天都在使用的HTTP协议，到底是什么鬼？</strong></a></p>
<h3 id="2、HTTPS简介"><a href="#2、HTTPS简介" class="headerlink" title="2、HTTPS简介"></a>2、HTTPS简介</h3><p>在HTTP的基础上再加一层TLS（传输层安全性协议）或者SSL（安全套接层），就构成了HTTPS协议。</p>
<p>HTTPS详细介绍可以参考<a href="https://zhuanlan.zhihu.com/p/158593966" target="_blank" rel="noopener"><strong>一文带你了解HTTPS</strong></a></p>
<h4 id="HTTPS如何保证可靠性"><a href="#HTTPS如何保证可靠性" class="headerlink" title="HTTPS如何保证可靠性"></a>HTTPS如何保证可靠性</h4><ul>
<li>对称加密以及非对称加密<ul>
<li>通过非对称加密生成密钥，后面通过这个密钥进行对称加密进行传输</li>
</ul>
</li>
<li>数字签名<ul>
<li>保证非对称加密时发送的公钥是被认证过的，是安全可靠的</li>
</ul>
</li>
<li>单向Hash算法</li>
</ul>
<p><strong>大致过程如下</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209163352.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="3、HTTPS传输过程"><a href="#3、HTTPS传输过程" class="headerlink" title="3、HTTPS传输过程"></a>3、HTTPS传输过程</h3><ul>
<li><p><strong>建立TCP连接</strong>（HTTP）</p>
</li>
<li><p><strong>将HTTP请求转换为HTTPS请求，转到HTTPS网站</strong></p>
<ul>
<li>因为一般人输入网址时，都是输入如<code>www.baidu.com</code>，而不会输入<code>https://www.baidu.com</code>。这时默认使用的是HTTP协议，<strong>浏览器会帮我们自动转换为HTTPS协议</strong></li>
</ul>
</li>
<li><p><strong>建立新的TCP连接</strong>(HTTPS)</p>
<ul>
<li>因为HTTP与HTTPS的端口不同。HTTP使用80端口，HTTPS使用443端口</li>
</ul>
</li>
<li><p><strong>完成一系列的协商工作</strong></p>
<ul>
<li>完成加密套件的协商和证书的身份确认，这次交互客户端和服务端会协商出相同的密钥交换算法、对称加密算法、内容一致性校验算法、证书签名算法等等。浏览器获取到证书之后，也要验证证书的有效性，是否过期是否撤销</li>
</ul>
</li>
<li><p><strong>浏览器获取CA域名</strong></p>
<ul>
<li>如果没有CA域名的缓存，还需要进行<strong>DNS解析</strong></li>
</ul>
</li>
<li><p><strong>再次建立新的TCP连接</strong>（CA域名）</p>
</li>
<li><p><strong>发送OCSP请求</strong></p>
<ul>
<li>OCSP全称是Online Certificate Status Protocol，在线证书状态协议，顾名思义用来获取证书状态的请求，这里的状态包括有效、过期、未知。并且可以宽限一段客户端访问证书的时间</li>
</ul>
</li>
<li><p><strong>进行密钥协商</strong></p>
</li>
</ul>
<p>经过以上过程后，便可以进行数据的对称加密传输了。</p>
<h2 id="五、查询MAC地址"><a href="#五、查询MAC地址" class="headerlink" title="五、查询MAC地址"></a>五、查询MAC地址</h2><p>这一步主要负责为打包好的<code>数据+TCP首部+IP首部</code>寻找传输路线，<strong>找到IP对应的物理机</strong>，这里会用到ARP协议。</p>
<h3 id="1、ARP协议"><a href="#1、ARP协议" class="headerlink" title="1、ARP协议"></a>1、ARP协议</h3><p>ARP（Address Resolution Protocol）即地址解析协议， 用于<strong>实现从 IP 地址到 MAC 地址的映射</strong>，即询问目标IP对应的MAC地址。</p>
<h3 id="2、ARP如何交互"><a href="#2、ARP如何交互" class="headerlink" title="2、ARP如何交互"></a>2、ARP如何交互</h3><p>ARP协议通过<strong>一问一答</strong>实现交互，但是问和答都有讲究，<strong>问是通过广播形式实现，答是通过单播形式。</strong></p>
<hr>
<p><strong>以上都是计算机网络的部分，接下来将介绍服务器如何接收与处理请求</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209203752.png" srcset="/img/loading.gif" alt=""></p>
<h2 id="六、请求在Tomcat中的处理流程"><a href="#六、请求在Tomcat中的处理流程" class="headerlink" title="六、请求在Tomcat中的处理流程"></a>六、请求在Tomcat中的处理流程</h2><p>Web 容器以<strong>进程</strong>的方式在计算机上运行，<strong>它主要负责接收请求，并将其投送至特定的应用</strong>，但Web容器并不属于计算机网络的组成部分。接下来将以Tomcat为例介绍Web容器的核心组件。</p>
<h3 id="1、Tomcat的核心组件"><a href="#1、Tomcat的核心组件" class="headerlink" title="1、Tomcat的核心组件"></a>1、Tomcat的核心组件</h3><p>Tomcat的核心组件主要有：<strong>Server、Service、Connector、Engine、Host和Context</strong>。</p>
<p><strong>一个Server可以包含多个Service，一个Service可以包含多个Connector，但只能包含一个Engine，一个Engine可以包含多个Host，一个Host可以包含多个Context</strong>。</p>
<p><strong>它们之间的关系如下图所示</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209200950.png" srcset="/img/loading.gif" alt=""></p>
<p><strong>配置文件的结构如下</strong></p>
<pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">Server</span>&gt;</span>                              
    <span class="hljs-tag">&lt;<span class="hljs-name">Service</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Engine</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Host</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Context</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Host</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Host</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Context</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Host</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Engine</span>&gt;</span>  
        <span class="hljs-tag">&lt;<span class="hljs-name">Connector</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Connector</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Service</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Server</span>&gt;</span></code></pre>



<h4 id="Server"><a href="#Server" class="headerlink" title="Server"></a>Server</h4><p>Server 是整个配置文件的<strong>唯一根元素</strong>，代表整个 Tomcat 容器。Server 内部可以包含多个 Service，其主要职责就是管理多个 Service，对外提供给客户端访问，同时维护所有 Service 的生命周期，包括初始化服务、结束服务、定位客户端要访问的 Service 等等。</p>
<h4 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h4><p>Service 的主要职责就是将 Engine 与 Connector 装配在一起对外提供服务。一个 Service 可以包含多个 Connector，但只能包含一个 Engine，<strong>其中 Connector 负责从客户端接收请求，Engine 负责处理 Connector 接收进来的请求。</strong></p>
<h4 id="Connector"><a href="#Connector" class="headerlink" title="Connector"></a>Connector</h4><p><strong>Connector是主要负责接收请求的组件</strong>。</p>
<p><strong>Tomcat有以下两种工作模式</strong></p>
<ul>
<li><p>作为Web服务器，直接接收客户端的请求</p>
</li>
<li><p>作为Java Web服务器，接收前置Web服务器的请求</p>
</li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209213925.png" srcset="/img/loading.gif" alt=""></p>
<p>每个 Service 可以有一个或多个 Connector，不同工作模式下，Tomcat 需要为各种类型的请求分别定义相应的 Connector，这样才能正确接收客户端对应协议的请求。定义 Connector 可以使用多种属性，某些属性只适用于某种特定的 Connector 类型。</p>
<p>一般说来，常见的 Connector 有 4 种类型</p>
<ul>
<li><strong>HTTP</strong></li>
<li><strong>HTTPS</strong></li>
<li><strong>AJP</strong></li>
<li><strong>Proxy</strong></li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201210125922.png" srcset="/img/loading.gif" alt=""></p>
<p>Connector作为通信接口，<strong>它为其所属特定的 Service 接收外部客户端请求，以及回送应答至外部客户端</strong>。具体职责包括创建 Request、Response 对象用于跟外部客户端交换数据，并<strong>将 Request 交给配套的 Engine 来处理</strong>。</p>
<h4 id="Engine"><a href="#Engine" class="headerlink" title="Engine"></a>Engine</h4><p>Engine 是 Service 组件中<strong>负责请求处理的组件</strong>，其内部可以包含多个 Host。Engine 从一个或多个 Connector 中接收请求并处理，并将处理结果封装成应答交给 Connector，最终回传给外部客户端。</p>
<h4 id="Host"><a href="#Host" class="headerlink" title="Host"></a>Host</h4><p><strong>Host 代表一个虚拟主机，它对应计算机网络上的一个实体</strong>。即某个在 DNS 服务器上注册过的域名或者 IP 地址，例如：<code>www.baidu.com</code>或 201.187.10.21。Host 内部可以包含多个 Context，<strong>每个 Context 表示一个 Web 应用</strong>。Host 负责安装、展开、启动和结束每个 Web 应用。</p>
<p>客户端在填写目标地址时会通过主机名来标识它希望访问的服务器，Tomcat 将从 HTTP 请求头的 Host 字段提取主机名，然后再匹配对应的虚拟主机。如果没有找到匹配的，HTTP 请求将被发送至默认主机 defaultHost。</p>
<h4 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h4><p>Context 代表在特定虚拟主机上运行的一个 Web 应用，<strong>负责处理某个特定 Web 应用的所有请求</strong>。</p>
<h3 id="2、Tomcat处理HTTP请求"><a href="#2、Tomcat处理HTTP请求" class="headerlink" title="2、Tomcat处理HTTP请求"></a>2、Tomcat处理HTTP请求</h3><p>当以 HTTP 请求到达Tomcat服务器（Server）以后，Tomcat会进行以下几个步骤，将请求交给对应的Web应用进行处理</p>
<ul>
<li><strong>根据协议类型和端口号选定 Service 和 Engine</strong><ul>
<li>Connector 主要负责接收请求。当 Connector 接收到特定协议和特定端口的请求后，<strong>其所属的 Service 和 Service 下的 Engine 也就确定了</strong></li>
</ul>
</li>
<li><strong>根据域名或 IP 地址选定 Host</strong><ul>
<li>Engine一旦确定了，就会根据 IP 来选择对应的虚拟主机Host来处理请求。如果匹配失败了，则会使用默认虚拟主机来处理请求</li>
</ul>
</li>
<li><strong>根据 URI 选定 Context</strong><ul>
<li>URI 中的 context-path 指定了 HTTPS 请求将要访问的 Web 应用</li>
<li>当请求抵达时，Tomcat 将根据 Context 的属性 path 取值与 URI 中的 context-path 的匹配程度来选择 Web 应用处理相应请求</li>
</ul>
</li>
</ul>
<h2 id="七、请求在Web应用中的处理流程"><a href="#七、请求在Web应用中的处理流程" class="headerlink" title="七、请求在Web应用中的处理流程"></a>七、请求在Web应用中的处理流程</h2><p>请求被 Web 容器中的 Connector 捕获，选取对应的 Server 中的 Engine ，Engine 再根据IP选择对应的虚拟主机，虚拟主机根据URI将请求交给对应的Web应用进行处理。接下来将介绍请求在Web请求中的处理过程。</p>
<p>介绍处理过程前，先对Web应用的基本组件进行简单介绍。</p>
<h3 id="1、Web应用核心组件"><a href="#1、Web应用核心组件" class="headerlink" title="1、Web应用核心组件"></a>1、Web应用核心组件</h3><h4 id="Listener"><a href="#Listener" class="headerlink" title="Listener"></a>Listener</h4><p>监听器 Listener 主要用于监听 Application、Session、Request 等对象的变化，每当这些对象发生变化就会回调用对应的监听方法。</p>
<h4 id="Filter"><a href="#Filter" class="headerlink" title="Filter"></a>Filter</h4><p>过滤器 Filter 负责对请求做<strong>预处理</strong>，接着将请求交给 Servlet 进行处理并生成响应，最后 Filter 再对响应进行后处理。</p>
<p>从请求的处理过程来看，Filter 主要参与以下几个环节</p>
<ul>
<li><p>在 HttpServletRequest <strong>到达 Servlet 之前，拦截客户的 HttpServletRequest</strong></p>
</li>
<li><p>根据需要检查 HttpServletRequest，也可以修改 HttpServletRequest 报文头和数据</p>
</li>
<li><p>在 Servlet 生成的 HttpServletResponse <strong>抵达客户端之前，拦截 HttpServletResponse</strong></p>
</li>
<li><p>根据需要检查 HttpServletResponse，也可以修改 HttpServletResponse 报文头和数据</p>
</li>
</ul>
<p><strong>简单来说就是在真正处理请求以及返回响应之前，通过过滤器对内容再进行一些修改</strong></p>
<h4 id="Servlet"><a href="#Servlet" class="headerlink" title="Servlet"></a>Servlet</h4><p><strong>Servlet 负责处理客户端访问动态资源的 HTTP 请求</strong>，接口 javax.servlet.Servlet 定义了所有 Servlet 必须要实现的方法</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Servlet</span> </span>&#123;
    <span class="hljs-comment">// 由 Servlet 容器调用，完成 Servlet 初始化，启动对外服务</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(ServletConfig var1)</span> <span class="hljs-keyword">throws</span> ServletException</span>;

    <span class="hljs-comment">// 获取 Servlet 初始化和启动时参数的配置信息对象 ServletConfig</span>
    <span class="hljs-function">ServletConfig <span class="hljs-title">getServletConfig</span><span class="hljs-params">()</span></span>;

    <span class="hljs-comment">// 由 Servlet 容器调用，让 Servlet 处理某个 HTTP 请求</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">service</span><span class="hljs-params">(ServletRequest var1, ServletResponse var2)</span> <span class="hljs-keyword">throws</span> ServletException, IOException</span>;

    <span class="hljs-comment">// 获取 Servlet 的说明信息，包括：作者、版本和版权等等</span>
    <span class="hljs-function">String <span class="hljs-title">getServletInfo</span><span class="hljs-params">()</span></span>;

    <span class="hljs-comment">// 由 Servlet 容器调用，用于关闭停止 Servlet 提供的服务</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">destroy</span><span class="hljs-params">()</span></span>;
&#125;</code></pre>



<p>从 HTTP 请求的处理过程来看，Servlet 主要参与以下几个环节</p>
<ul>
<li><strong>接收请求</strong><ul>
<li>客户端请求会被封装成 HttpServletRequest 对象，包含报文头参数和报文体等信息</li>
</ul>
</li>
<li><strong>处理请求</strong><ul>
<li>通常调用 Servlet 的方法 service、doPost 或 doGet 等方法处理请求，并<strong>进一步调用业务层相应逻辑对其进行处理等</strong></li>
</ul>
</li>
<li><strong>反回响应</strong><ul>
<li>处理完请求后，可以转发（forward）、重定向（redirect）到某个视图页面或者直接返回结果数据</li>
</ul>
</li>
</ul>
<h3 id="2、Web应用处理HTTP请求流程"><a href="#2、Web应用处理HTTP请求流程" class="headerlink" title="2、Web应用处理HTTP请求流程"></a>2、Web应用处理HTTP请求流程</h3><p>Web 应用处理 HTTP 请求的流程主要是<strong>穿越 Listener 和多个 Filters，最终抵达 Servlet 的过程</strong>，Servlet再进行下一步的处理。</p>
<p><strong>具体流程如下图</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201209213603.png" srcset="/img/loading.gif" alt=""></p>
<h2 id="八、请求在Spring-Web应用中的处理流程"><a href="#八、请求在Spring-Web应用中的处理流程" class="headerlink" title="八、请求在Spring Web应用中的处理流程"></a>八、请求在Spring Web应用中的处理流程</h2><p>因为使用 SSM 框架，所以 Spring MVC 中的 DispatcherServlet 充当了 Web 应用中的 Serlvet，负责将任务分配给对应的Controller，并将最终视图返回给 Web 容器。</p>
<h3 id="1、Spring-MVC的核心组件"><a href="#1、Spring-MVC的核心组件" class="headerlink" title="1、Spring MVC的核心组件"></a>1、Spring MVC的核心组件</h3><h4 id="DispatcherServlet"><a href="#DispatcherServlet" class="headerlink" title="DispatcherServlet"></a>DispatcherServlet</h4><p>DispatcherServlet 是整个流程<strong>控制的中心</strong>，由它来<strong>接收请求并调用其它组件处理用户的请求</strong>，同时还负责响应结果。DispatcherServlet的存在降低了组件之间的耦合性。</p>
<h4 id="HandlerMapping"><a href="#HandlerMapping" class="headerlink" title="HandlerMapping"></a>HandlerMapping</h4><p>HandlerMapping 负责<strong>根据用户请求映射获得对应的 Handler和 HandlerInterceptor</strong>。处理方法为从 URL 获得 URI，在通过 URI 从 HandlerMapping 中找到对应的 Handler 和 HandlerInterceptor，即处理器和拦截器。</p>
<h4 id="HandlerAdapter"><a href="#HandlerAdapter" class="headerlink" title="HandlerAdapter"></a>HandlerAdapter</h4><p>HandlerAdapter 负责按照特定规则去执行 Handler。</p>
<p>如果 Handler 有对应的 HandlerAdapater，<strong>HandlerAdapater 则会在调用 Handler 之前执行 HandlerInterceptor 的 preHandler() 方法对 Handler 进行拦截</strong>。</p>
<h4 id="HandlerInterceptor"><a href="#HandlerInterceptor" class="headerlink" title="HandlerInterceptor"></a>HandlerInterceptor</h4><p>HandlerInterceptor 主要负责在执行 Handler 前对其进行拦截。HandlerInterceptor 中的 preHandler() 方法将会提取 HTTP 请求中的数据填充到处理器 Handler 的中。</p>
<h4 id="Handler"><a href="#Handler" class="headerlink" title="Handler"></a>Handler</h4><p>Handler <strong>即Controller ，是处理业务代码的核心器件</strong>。这部分由程序员自行编写，一般的SSM框架中，其下层还有Service和Dao。</p>
<h3 id="2、Spring-MVC处理请求流程"><a href="#2、Spring-MVC处理请求流程" class="headerlink" title="2、Spring MVC处理请求流程"></a>2、Spring MVC处理请求流程</h3><p>当 Web 容器中的 Host 会选择对应的 Web应用来处理请求，这里将请求交给了 Spring MVC 中的 DispatcherServlet 来进一步处理请求。</p>
<ul>
<li><p>DispatcherServlet 通过解析 HTTP 请求的 URL 获得 URI，再根据该 URI <strong>从 HandlerMapping 当中获得该请求对应的 Handler 和 HandlerInterceptor</strong></p>
</li>
<li><p>DispatcherServlet 根据获得的 Handler 选择合适的 HandlerAdapter。如果成功获得 HandlerAdapter，<strong>HandlerAdapater 则会在调用 Handler 之前执行 HandlerInterceptor 的 preHandler() 方法对 Handler 进行拦截</strong></p>
</li>
<li><p>Handler 即 Controller 会进行请求的处理，并向下调用 Service 和 Dao 来处理请求</p>
</li>
<li><p>Hander 处理完成请求后会返回模型数据，模型数据由 DispatcherServlet 封装后返回给Web 容器</p>
</li>
</ul>
<p><strong>处理的流程图如下</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201210105723.png" srcset="/img/loading.gif" alt=""></p>
<h2 id="九、返回过程"><a href="#九、返回过程" class="headerlink" title="九、返回过程"></a>九、返回过程</h2><p>Web 应用处理完请求并将结果返回给 Web 容器后，容器会将响应结果返回给客户端，这是上面流程的逆过程。浏览器收到响应结果后，会对结果进行解析和渲染。这样我们就能看到浏览器给我们显示的网页了。</p>
<h2 id="十、整体流程图"><a href="#十、整体流程图" class="headerlink" title="十、整体流程图"></a>十、整体流程图</h2><p>下面给出了输入URL到浏览器显示界面的流程图</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20201210113745.png" srcset="/img/loading.gif" alt=""></p>
<p><strong>以上便是从输入URL到浏览器显示页面的整个流程</strong></p>
<h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a><strong>参考文献</strong></h2><p><a href="https://mp.weixin.qq.com/s/AK1Pb9rx0q5Hf8dq6HNOhw" target="_blank" rel="noopener">你每天都在使用的HTTP协议，到底是什么鬼？</a></p>
<p><a href="https://segmentfault.com/a/1190000021137583" target="_blank" rel="noopener">图解 Spring：HTTP 请求的处理流程与机制</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/80551769" target="_blank" rel="noopener">在浏览器输入 URL 回车之后发生了什么（超详细版）</a></p>
<p><a href="https://juejin.cn/post/6844903832435032072" target="_blank" rel="noopener">史上最详细的经典面试题 从输入URL到看到页面发生了什么？</a></p>

            </article>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/categories/%E5%8E%9F%E7%90%86/">原理</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/">计算机网络</a>
                    
                  </div>
                
              </div>
              
                <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！</p>
              
              
                <div class="post-prevnext row">
                  <div class="post-prev col-6">
                    
                    
                  </div>
                  <div class="post-next col-6">
                    
                    
                      <a href="/2020/11/30/Java%20NIO/">
                        <span class="hidden-mobile">Java NIO</span>
                        <span class="visible-mobile">Next</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </div>
                </div>
              
            </div>

            
              <!-- Comments -->
              <div class="comments" id="comments">
                
                
  <div class="disqus" style="width:100%">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
      function loadDisqus() {
        var disqus_config = function () {
          this.page.url = 'http://nyimac.gitee.io/2020/12/10/URL访问服务器流程/';
          this.page.identifier = '/2020/12/10/URL访问服务器流程/';
        };
        (function () {
          var d = document,
            s = d.createElement('script');
          s.src = '//' + '' + '.disqus.com/embed.js';
          s.setAttribute('data-timestamp', new Date());
          (d.head || d.body).appendChild(s);
        })();
      }
      createObserver(loadDisqus, 'disqus_thread');
    </script>
    <noscript>Please enable JavaScript to view the
      <a href="https://disqus.com/?ref_noscript" target="_blank" rel="nofollow noopener noopener">comments powered by Disqus.</a>
    </noscript>
  </div>


              </div>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;TOC</p>
  <div id="tocbot"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    
  </main>

  
    <a id="scroll-top-button" href="#" role="button">
      <i class="iconfont icon-arrowup" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">Search</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">keyword</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  

  

  <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
      <i class="iconfont icon-love"></i>
      <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener">
        <span>Fluid</span></a>
    </div>
    
  <div class="statistics">
    
    

    
      
        <!-- 不蒜子统计PV -->
        <span id="busuanzi_container_site_pv" style="display: none">
            总访问量 
            <span id="busuanzi_value_site_pv"></span>
             次
          </span>
      
      
        <!-- 不蒜子统计UV -->
        <span id="busuanzi_container_site_uv" style="display: none">
            总访客数 
            <span id="busuanzi_value_site_uv"></span>
             人
          </span>
      
    
  </div>


    

    
  </div>
</footer>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="/js/debouncer.js" ></script>
<script  src="/js/main.js" ></script>

<!-- Plugins -->


  
    <script  src="/js/lazyload.js" ></script>
  



  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/js/clipboard-use.js" ></script>



  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





  <script  src="https://cdn.staticfile.org/tocbot/4.11.1/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var boardCtn = $('#board-ctn');
      var boardTop = boardCtn.offset().top;

      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: 'article.markdown-body',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        collapseDepth: 0,
        scrollSmooth: true,
        headingsOffset: -boardTop
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc').css('visibility', 'visible');
      }
    });
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "从输入URL到浏览器显示页面的流程&nbsp;",
      ],
      cursorChar: "_",
      typeSpeed: 70,
      loop: false,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/js/local-search.js" ></script>
  <script>
    var path = "/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      searchFunc(path, 'local-search-input', 'local-search-result');
      this.onclick = null
    }
  </script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />

  <script>
    $('#post img:not(.no-zoom img, img[no-zoom]), img[zoom]').each(
      function () {
        var element = document.createElement('a');
        $(element).attr('data-fancybox', 'images');
        $(element).attr('href', $(this).attr('src'));
        $(this).wrap(element);
      }
    );
  </script>

















  
    <!-- Baidu Analytics -->
    <script defer>
      var _hmt = _hmt || [];
      (function () {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?ba41ec605b9b7320e120275462e4035b";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
      })();
    </script>
  

  

  

  

  

  





</body>
</html>
